package com.appspot.MercerBroker;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import java.util.Properties;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.common.base.Optional;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;

import static com.google.common.base.Preconditions.*;


public class BrokerServlet extends HttpServlet 
{
	private static final long serialVersionUID = 140540356733116037L;

	private static Driver _driver;
	
	private Optional<Connection> _db = Optional.absent();
	
	private final static Map<String,String> _dbInfo = Maps.newHashMap();

	private final static ImmutableMap<String,String> _queries = 
			ImmutableMap.of( 					
					"list-qry", 
					"SELECT `name`, `address`, `added` FROM servers "
					+ "ORDER BY `added` DESC "
					+ " LIMIT 1000;",
					
					"update-qry",
					"REPLACE INTO servers (`name`,`address`) VALUES (?,?)",
					
					"pick-qry",					
					"SELECT `name`, `address`, `added` FROM servers "
					+ "WHERE `name` = (?) "
					+ " LIMIT 1",
					
					"purge-qry",
					"DELETE FROM servers WHERE `name` = (?) LIMIT 1" );

	private String _msgResp = "";


	@Override
	public void init()
	{
		
	}//method
	
	
	@Override
	public void doGet(  final HttpServletRequest req, 
			    		final HttpServletResponse resp ) throws IOException 
	{
		doPost( req, resp );
		
	}//method
	
	
	@Override
	public void doPost( final HttpServletRequest req, 
		  			    final HttpServletResponse resp ) throws IOException 
    {		
		if( req.getParameter("server-web-address") != null && 
			req.getParameter("server-web-address").length() > 0 )
		{
			/*
			 * servers register their addresses
			 */

			_msgResp = "Registration communication from server. "
					+ "\nname : " 
					+ req.getParameter("server-name") 
					+ "\naddress : "
					+ req.getParameter("server-web-address");
			
			serverInfoDB( req.getParameter("server-name"), 
						  req.getParameter("server-web-address"),
						  "update-qry" );
			
		}
		else if( req.getParameter("remove-server") != null && 
				 req.getParameter("remove-server").length() > 0 )
		{

			_msgResp = "Request to remove server: " 
					+ req.getParameter("remove-server");
			
			serverInfoDB( req.getParameter("remove-server"), 
					  "",
					  "purge-qry" );
			
		}		
		else if( req.getParameter("client-seeking") != null && 
				 req.getParameter("client-seeking").length() > 0 )
		{
			/*
			 * clients request a server
			 */
			
			_msgResp = "Communication from client: " 
					+ req.getParameter("client-seeking");
			
			/*
			 * this broker - accesses server if registered
			 * 			   - obtains response or exceptions
			 * 			   - echoes to client
			 */
			
			final String serverAddressFound = serverInfoDB( 
					req.getParameter("client-seeking"), 
					"", 
					"pick-qry" );
		
			if( serverAddressFound.length() < 1 )
			{ 
				_msgResp += "\nnot found in broker registry";
			
			}
			else
			{
				_msgResp += "\nserver last known address in broker registry: " 
						+ serverAddressFound;
				
				_msgResp += "\ncontent retrieved:\n" + fetchURLContents(
						serverAddressFound);
				
			}//if
			
		}
		else
		{
			/* default to showing the servers which have registered */
			
			_msgResp = "list of registered servers\n";
			
			_msgResp += serverInfoDB( "*", "", "list-qry" );
			
		}//if
		
		resp.setContentType( "text/plain" );
		resp.getOutputStream().println( "" + _msgResp );
			
    }//method
	
	
	private String fetchURLContents( final String url )
	{
		checkNotNull( url, "null url" );
		
		checkArgument( url.length() > 0, "empty url" );
		
		String urlContents = "";
		
		try
		{
			URL oracle = new URL(url);
			BufferedReader in = new BufferedReader(
					new InputStreamReader(oracle.openStream()));

			StringBuilder results = new StringBuilder();

			String line;
			while(( line = in.readLine()) != null )
				results.append( line + "\n" );
			in.close();
			
			urlContents = results.toString();
		
		}
		catch( final IOException err )
		{
			urlContents = "Unable to access: " + url;
			
		}//try
	        
		return urlContents;
		
	}//method

	
	private String serverInfoDB( final String serverName, 
					   @Nullable final String serverAddress,
						 	     final String queryKey )
	{
		StringBuilder results = new StringBuilder(queryKey);
		
		try
		{
			checkNotNull( serverName, "null server name" );
			
			checkArgument( _queries.containsKey(queryKey), 
						   
						   "unrecognized query index: " + queryKey );

			final PreparedStatement dbPrepState = get_db()
					.prepareStatement( _queries.get( queryKey ));

			if( !serverName.equals("*") ) 
				dbPrepState.setString( 1, serverName );
			
			if( serverAddress != null 
				&& 
				serverAddress.length() > 0 )
			{
				dbPrepState.setString( 2, serverAddress );
				
			}//if

			if( queryKey.equals("purge-qry") || 
				queryKey.equals("update-qry") ) 
			{
				dbPrepState.executeUpdate();
			
			}
			else
			{
				final ResultSet dbResults = dbPrepState.executeQuery();

				if( serverName.equals("*") )
				{
					while( dbResults.next() )
					{
						results.append( "\n" );
						results.append( dbResults.getString("name") );
						results.append( " = " );
						results.append( dbResults.getString("address") );
						results.append( " : " );
						results.append( dbResults.getString("added") );					
						results.append( "\n" );
						
					}//while
				
				}
				else if( queryKey.equals("pick-qry") )
				{
					results.setLength(0);
					if( dbResults.next() ) 
					{
						results.append( dbResults.getString("address") );
					
					}
					else
					{
						results.append( "not found" );
						
					}//if
					
				}//if

				dbResults.close();
				
			}//if
			
			dbPrepState.close();
			
		}
		 catch( final SQLException err )
		 {
			 Throwables.propagate( err );

		 }//try		 
		 
		return results.toString();
		
	}//method
	

	private Connection connectDb()
	{
		try
		{
			_driver =  new com.mysql.jdbc.Driver();
			
			DriverManager.registerDriver( _driver );
			
			final Properties properties = new Properties();
			
			properties.load(Thread
					.currentThread()
					.getContextClassLoader()
					.getResourceAsStream("jdbc_config.properties"));
			
			for( Object propertyName : properties.keySet() )
			{
				String propertyNameStr = (String) propertyName;
				
				_dbInfo.put( propertyNameStr, 
							 properties.getProperty(propertyNameStr) );
				
			}//for
			
			set_db( DriverManager.getConnection(
					
					_dbInfo.get("jdbc_url") + "?",
					
					_dbInfo.get("jdbc_user"),
					
					_dbInfo.get("jdbc_pw")));			
			
		}
		catch( final IOException err )
		{
			Throwables.propagate( err );
			
		}
		catch( final SQLException err )
		{
			Throwables.propagate( err );
		
		}//try

		if( !_db.isPresent() ) throw new RuntimeException("failure database "
				+ "connection initialization");
		
		return _db.get();
		
	}//method
	
	
	private Connection get_db()
	{
		return _db.or( connectDb() );
		
	}//method


	private void set_db( final Connection dbConn )
	{
		checkNotNull( dbConn, "null database connection" );
		
		this._db = Optional.of( dbConn );
		
	}//method
  
	
	@Override
	public void destroy()
	{
		try
		{
			DriverManager.deregisterDriver( _driver );

		}
		catch( final SQLException err )
		{
			Throwables.propagate( err );

		}//try		
		
	}//method

	
}//class
